﻿namespace LambdaCalc.Parser.Tests

open System
open NUnit.Framework

open LambdaCalc.Parser

[<TestFixture>]
type ``test simple evaluation of lambda-terms``() = 

    [<Test>]
    member test.``'a' parses to a evaluation of Atom a``() =
        let input = "a"
        let expectedOutput = Atom "a" |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'(a)' parses to a evaluation of Atom a``() =
        let input = "(a)"
        let expectedOutput = Atom "a" |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'a31' parses to a evaluation of Atom a11``() =
        let input = "a31"
        let expectedOutput = Atom "a31" |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'A23' parses to a evaluation of Identifier A23``() =
        let input = "A23"
        let expectedOutput = Identifier "A23" |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)


    [<Test>]
    member test.``'Alt23' parses to a evaluation of Identifier Alt23``() =
        let input = "Alt23"
        let expectedOutput = Identifier "Alt23" |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'ab' parses to a evaluation of Application (a, b)``() =
        let input = "ab"
        let expectedOutput = Application [Atom "a"; Atom "b"] |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)


    [<Test>]
    member test.``'a b' parses to a evaluation of Application (a, b)``() =
        let input = "a b"
        let expectedOutput = Application [Atom "a"; Atom "b"] |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'(ab)' parses to a evaluation of Application (a, b)``() =
        let input = "(ab)"
        let expectedOutput = Application [Atom "a"; Atom "b"] |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'(ab)c' parses to a evaluation of App(App(a, b), c)``() =
        let input = "(ab)c"
        let expectedOutput = Application [Application [Atom "a"; Atom "b"]; Atom "c"] 
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)    
        
    [<Test>]
    member test.``'abcd' parses to a evaluation of App(App(App(a, b), c), d)``() =
        let input = "abcd"
        let expectedOutput = Application [Atom "a"; Atom "b"; Atom "c"; Atom "d"] 
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)
        
    [<Test>]
    member test.``'(ab)cd' parses to a evaluation of App(App(a, b), c, d)``() =
        let input = "(ab)cd"
        let expectedOutput = Application [Application [Atom "a"; Atom "b"]; Atom "c"; Atom "d"] 
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'(ab)(cd)' parses to a evaluation of App(App(a, b), App (c, d))``() =
        let input = "(ab)(cd)"
        let expectedOutput = Application [Application [Atom "a"; Atom "b"]; 
                                          Application [Atom "c"; Atom "d"]]
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)    
        
    [<Test>]
    member test.``'(ab) c' parses to a evaluation of App(App(a, b),c)``() =
        let input = "(ab) c"
        let expectedOutput = Application [Application [Atom "a"; Atom "b"]; 
                                          Atom "c"]
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)    
        
    [<Test>]
    member test.``'(ab)C' parses to a evaluation of App(App(a, b), ID c)``() =
        let input = "(ab)C"
        let expectedOutput = Application [Application [Atom "a"; Atom "b"]; 
                                          Identifier "C"]
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)    
        
    [<Test>]
    member test.``'(ab) C' parses to a evaluation of App(App(a, b), ID c)``() =
        let input = "(ab) C"
        let expectedOutput = Application [Application [Atom "a"; Atom "b"]; 
                                          Identifier "C"]
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)    
        
    [<Test>]
    member test.``'(a)(cd)' parses to a evaluation of App(a, App (c, d))``() =
        let input = "(a)(cd)"
        let expectedOutput = Application [Atom "a";  
                                          Application [Atom "c"; Atom "d"]]
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)        

    [<Test>]
    member test.``'a(cd)' parses to a evaluation of App(a, App (c, d))``() =
        let input = "a(cd)"
        let expectedOutput = Application [Atom "a";  
                                          Application [Atom "c"; Atom "d"]]
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'\x.x' parses to a evaluation of Abstraction([x], Atom x)``() =
        let input = "\x.x"
        let expectedOutput = Abstraction(["x"], Atom "x") |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'\x.(x)' parses to a evaluation of Abstraction([x], Atom x)``() =
        let input = "\x.(x)"
        let expectedOutput = Abstraction(["x"], Atom "x") |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'\x.xy' parses to a evaluation of Abstraction([x], Application(Atom x, Atom y)``() =
        let input = "\x.xy"
        let expectedOutput = Abstraction(["x"], Application [Atom "x"; Atom "y"]) |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'\x.\y.xy' parses to a evaluation of Abstraction([x], Abstraction [y], Application(Atom x, Atom y))``() =
        let input = "\x.\y.xy"
        let expectedOutput = Abstraction(["x"], 
                                Abstraction(["y"], Application [Atom "x"; Atom "y"]))
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'\xy.xy' parses to a evaluation of Abstraction([x;y], Application(Atom x, Atom y))``() =
        let input = "\xy.xy"
        let expectedOutput = Abstraction(["x"; "y"], 
                                Application [Atom "x"; Atom "y"])
                             |> Evaluation
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'I:=\x.x' parses to an assignment of the identity function to the Identifiername I``() =
        let input = "I:=\x.x"
        let expectedOutput = ("I", 
                              Abstraction(["x"], Atom "x"))
                             |> Assignment 
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``assignment ignores whitespaces: 'I := \x.x' parses to an assignment of the identity function to the Identifiername I``() =
        let input = "I := \x.x"
        let expectedOutput = ("I", 
                              Abstraction(["x"], Atom "x"))
                             |> Assignment 
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``'\x.(\i.i) a' should be parsed into Abstraction(x, Application(Abstraction(i, Atom i), Atom a))``() =
        let input = "\x.(\i.i) a"
        let expectedOutput = Abstraction(["x"], Application [Abstraction(["i"], Atom "i"); Atom "a"])
                             |> Evaluation 
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)

    [<Test>]
    member test.``direct-assignment does not do any reduction steps``() =
        let input = "I ::= (\x.x) f"
        let expectedOutput = ("I", 
                              Application [Abstraction(["x"], Atom "x"); Atom "f"])
                             |> DirectAssignment 
        let parserResult = LambdaParser.parse input
        Assert.AreEqual(expectedOutput, parserResult)


        